#include <linux/input.h>
#include "event.h"
#include <tinyaml/tinyaml.h>
#include <tinyaml/extension/IO/file.h>

/*#define EVDEV_MAGIC 0x6106EDED*/
#define EVDEV_MAGIC 0x6106F11E


void event_deinit(vm_t vm, event_t e) {
	if(e->value.type&DataManagedObjectFlag) {
		vm_obj_deref_ptr(vm, (void*)e->value.data);
	}
}

event_t event_clone(vm_t vm, event_t src) {
	event_t event = event_new(vm);
	if(src->value.type&DataManagedObjectFlag) {
		vm_obj_ref_ptr(vm, (void*)src->value.data);
	}
	event->type = src->type;
	event->code = src->code;
	event->value.type = src->value.type;
	event->value.data = src->value.data;
	return event;
}


event_t event_new(vm_t vm) {
	event_t event = (event_t) vm_obj_new(sizeof(struct _event_t),
				(void (*)(vm_t, void*)) event_deinit,
				(void*(*)(vm_t, void*)) event_clone,
				DataObjUser);
	memset(event, 0, sizeof(struct _event_t));
	event->magic = EVENT_MAGIC;
	return event;
}




void pack_event(file_t f, char fmt, event_t ev) {
	struct input_event ie;
	ie.type = ev->type;
	ie.code = ev->code;
	assert(ev->value.type==DataInt);
	ie.value = ev->value.data;
	write(f->descr.fd, &ie, sizeof(struct input_event));
}

event_t unpack_event(file_t f, char fmt) {
	size_t rb;
	struct input_event ie;
	event_t event;
	rb=read(f->descr.fd, &ie, sizeof(struct input_event));
	if(rb!=sizeof(struct input_event)) {
		char message[512];
		sprintf(message, "Short read in %s", f->source);
		vm_fatal(message);
	}
	event = event_new(_glob_vm);
	event->type = ie.type;
	event->code = ie.code;
	event->value.type = DataInt;
	event->value.data = ie.value;
	return event;
}

word_t fail_unpack(file_t f, char fmt) {
	static char buf[32] = "  format char not allowed";
	buf[0] = fmt;
	vm_fatal(buf);
	return 0;
}

void fail_pack(file_t f, char fmt, word_t unused) {
	static char buf[32] = "  format char not allowed";
	buf[0] = fmt;
	vm_fatal(buf);
}

void* dont_clone(vm_t vm, void* ptr) {
	return ptr;
}

void file_deinit(vm_t, file_t);

void _VM_CALL vm_op_evdev_open(vm_t vm, word_t unused) {
	vm_data_t d = _vm_pop(vm);
	const char* filename = (const char*)d->data;
	int fmt;
	file_t ret = (file_t) vm_obj_new(sizeof(struct _file_t),
			(void (*)(vm_t,void*)) file_deinit,
			(void*(*)(vm_t,void*)) dont_clone,
			DataObjUser);
	assert(d->type==DataString||d->type==DataObjStr);
	ret->magic = EVDEV_MAGIC;
	ret->descr.fd = open(filename, O_RDWR);
	ret->owner = vm->current_thread;
	ret->flags=0;
	for(fmt=0;fmt<127;fmt+=1) {
		/*file_override_format(ret, fmt, fail_pack, fail_unpack, DataInt, 0);*/
		ret->_overrides[fmt]=NULL;
	}
	file_override_format(ret, 'e', (_pack_handler)pack_event, (_unpack_handler)unpack_event, DataObjUser, EVENT_MAGIC);
	ret->source = strdup(filename?filename:"(unset)");
	file_update_state(ret, FISOPEN|FREADABLE|FWRITABLE);
	vm_push_data(vm, DataObjUser, (word_t)ret);
}

#define _fd(_ed) ((_ed)->descr.fd)
#define ioctl_(_f, args...) if(ioctl(_fd(_f) , ## args)<0) { perror("ioctl " #args); }

void _VM_CALL vm_op_evdev_get_version(vm_t vm, word_t unused) {
	int version=0;
	vm_data_t d = _vm_pop(vm);
	file_t ed = (file_t) d->data;
	assert(d->type==DataObjUser&&ed->magic==EVDEV_MAGIC);
	ioctl_(ed, EVIOCGVERSION, &version);
	vm_push_data(vm, DataInt, version&0xFF);			/* patch */
	vm_push_data(vm, DataInt, (version>>8)&0xFF);			/* minor */
	vm_push_data(vm, DataInt, (version>>16)&0xFFFF);		/* major */
}


extern void _VM_CALL vm_op_mapSet_String(vm_t, word_t);

void _VM_CALL vm_op_evdev_get_id(vm_t vm, word_t unused) {
	struct input_id id;
	vm_data_t d = _vm_pop(vm);
	vm_dyn_env_t map;
	file_t ed = (file_t) d->data;
	assert(d->type==DataObjUser&&ed->magic==EVDEV_MAGIC);
	ioctl_(ed, EVIOCGID, &id);
	map = vm_env_new();
	vm_push_data(vm, DataInt, (word_t) id.bustype); vm_push_data(vm, DataObjEnv, (word_t) map); vm_op_mapSet_String(vm, (word_t) "bustype");
	vm_push_data(vm, DataInt, (word_t) id.vendor); vm_push_data(vm, DataObjEnv, (word_t) map); vm_op_mapSet_String(vm, (word_t) "vendor");
	vm_push_data(vm, DataInt, (word_t) id.product); vm_push_data(vm, DataObjEnv, (word_t) map); vm_op_mapSet_String(vm, (word_t) "product");
	vm_push_data(vm, DataInt, (word_t) id.version); vm_push_data(vm, DataObjEnv, (word_t) map); vm_op_mapSet_String(vm, (word_t) "version");
	vm_push_data(vm, DataObjEnv, (word_t) map);
}


void _VM_CALL vm_op_evdev_get_repeat(vm_t vm, word_t unused) {
	vm_data_t d = _vm_pop(vm);
	file_t ed = (file_t) d->data;
	int rep[2]={0,0};
	assert(d->type==DataObjUser&&ed->magic==EVDEV_MAGIC);
	ioctl_(ed, EVIOCGREP, rep);
	vm_push_data(vm, DataInt, (word_t) rep[1]);
	vm_push_data(vm, DataInt, (word_t) rep[0]);
}


void _VM_CALL vm_op_evdev_set_repeat(vm_t vm, word_t unused) {
	int rep[2];
	vm_data_t r2 = _vm_pop(vm);
	vm_data_t r1 = _vm_pop(vm);
	vm_data_t d = _vm_pop(vm);
	file_t ed = (file_t) d->data;
	assert(d->type==DataObjUser&&ed->magic==EVDEV_MAGIC);
	assert(r1->type==DataInt);
	assert(r2->type==DataInt);
	rep[0] = (int) r1->data;
	rep[1] = (int) r2->data;
	ioctl_(ed, EVIOCSREP, rep);
}


void _VM_CALL vm_op_evdev_get_keycode(vm_t vm, word_t unused) {
	int codes[2];
	vm_data_t d = _vm_pop(vm);
	file_t ed = (file_t) d->data;
	assert(d->type==DataObjUser&&ed->magic==EVDEV_MAGIC);
	ioctl_(ed, EVIOCGKEYCODE, codes);
	vm_push_data(vm, DataInt, (word_t) codes[1]);
	vm_push_data(vm, DataInt, (word_t) codes[0]);
}


void _VM_CALL vm_op_evdev_set_keycode(vm_t vm, word_t unused) {
	int codes[2];
	vm_data_t c2 = _vm_pop(vm);
	vm_data_t c1 = _vm_pop(vm);
	vm_data_t d = _vm_pop(vm);
	file_t ed = (file_t) d->data;
	assert(d->type==DataObjUser&&ed->magic==EVDEV_MAGIC);
	assert(c1->type==DataInt);
	assert(c2->type==DataInt);
	codes[0] = (int) c1->data;
	codes[1] = (int) c2->data;
	ioctl_(ed, EVIOCGKEYCODE, codes);
}


#define NLEN 256

void _VM_CALL vm_op_evdev_get_name(vm_t vm, word_t unused) {
	char name[NLEN]="Unknown";
	vm_data_t d = _vm_pop(vm);
	file_t ed = (file_t) d->data;
	assert(d->type==DataObjUser&&ed->magic==EVDEV_MAGIC);
	ioctl_(ed, EVIOCGNAME(NLEN), name);
	vm_push_data(vm, DataObjStr, (word_t) vm_string_new(name));
}


void _VM_CALL vm_op_evdev_get_location(vm_t vm, word_t unused) {
	char name[NLEN]="Unknown";
	vm_data_t d = _vm_pop(vm);
	file_t ed = (file_t) d->data;
	assert(d->type==DataObjUser&&ed->magic==EVDEV_MAGIC);
	ioctl_(ed, EVIOCGPHYS(NLEN), name);
	vm_push_data(vm, DataObjStr, (word_t) vm_string_new(name));
}



void _VM_CALL vm_op_evdev_get_unique_id(vm_t vm, word_t unused) {
	char name[NLEN]="Unknown";
	vm_data_t d = _vm_pop(vm);
	file_t ed = (file_t) d->data;
	assert(d->type==DataObjUser&&ed->magic==EVDEV_MAGIC);
	ioctl_(ed, EVIOCGUNIQ(NLEN), name);
	vm_push_data(vm, DataObjStr, (word_t) vm_string_new(name));
}


void _VM_CALL vm_op_evdev_get_global_keystate(vm_t vm, word_t unused) {
	vm_data_t d = _vm_pop(vm);
	file_t ed = (file_t) d->data;
	dynarray_t da;
	assert(d->type==DataObjUser&&ed->magic==EVDEV_MAGIC);
	da = vm_array_new();
	#define DASZ (KEY_MAX/(8*sizeof(word_t)) + 1)
	dynarray_reserve(da, DASZ);
	ioctl_(ed, EVIOCGKEY(DASZ*sizeof(word_t)), da->data);
	vm_push_data(vm, DataObjArray, (word_t) da);
	#undef DASZ
}


void _VM_CALL vm_op_evdev_get_LEDs(vm_t vm, word_t unused) {
	vm_data_t d = _vm_pop(vm);
	file_t ed = (file_t) d->data;
	dynarray_t da;
	assert(d->type==DataObjUser&&ed->magic==EVDEV_MAGIC);
	da = vm_array_new();
	#define DASZ (LED_MAX/(8*sizeof(word_t)) + 1)
	dynarray_reserve(da, DASZ);
	ioctl_(ed, EVIOCGKEY(DASZ*sizeof(word_t)), da->data);
	vm_push_data(vm, DataObjArray, (word_t) da);
	#undef DASZ
}


void _VM_CALL vm_op_evdev_get_snd_status(vm_t vm, word_t unused) {
	vm_data_t d = _vm_pop(vm);
	file_t ed = (file_t) d->data;
	assert(d->type==DataObjUser&&ed->magic==EVDEV_MAGIC);
}


void _VM_CALL vm_op_evdev_get_event_bits(vm_t vm, word_t unused) {
	vm_data_t d = _vm_pop(vm);
	file_t ed = (file_t) d->data;
	assert(d->type==DataObjUser&&ed->magic==EVDEV_MAGIC);
}


void _VM_CALL vm_op_evdev_get_abs_axis(vm_t vm, word_t unused) {
}


void _VM_CALL vm_op_evdev_set_abs_axis(vm_t vm, word_t unused) {
}


void _VM_CALL vm_op_evdev_grab(vm_t vm, word_t unused) {
}









void _VM_CALL vm_op_evNew(vm_t vm, word_t unused) {
	vm_push_data(vm, DataObjUser, (word_t) event_new(vm));
}


void _VM_CALL vm_op_evGetDate(vm_t vm, word_t unused) {
	vm_data_t d = _vm_pop(vm);
	event_t ev = (event_t) d->data;
	assert(d->type==DataObjUser&&ev->magic==EVENT_MAGIC);
	vm_push_data(vm, DataFloat, ev->timestamp);
}


void _VM_CALL vm_op_evGetType(vm_t vm, word_t unused) {
	vm_data_t d = _vm_pop(vm);
	event_t ev = (event_t) d->data;
	assert(d->type==DataObjUser&&ev->magic==EVENT_MAGIC);
	vm_push_data(vm, DataInt, ev->type);
}


void _VM_CALL vm_op_evGetCode(vm_t vm, word_t unused) {
	vm_data_t d = _vm_pop(vm);
	event_t ev = (event_t) d->data;
	assert(d->type==DataObjUser&&ev->magic==EVENT_MAGIC);
	vm_push_data(vm, DataInt, ev->code);
}


void _VM_CALL vm_op_evGetValue(vm_t vm, word_t unused) {
	vm_data_t d = _vm_pop(vm);
	event_t ev = (event_t) d->data;
	assert(d->type==DataObjUser&&ev->magic==EVENT_MAGIC);
	vm_push_data(vm, ev->value.type, ev->value.data);
}


void _VM_CALL vm_op_evSetDate(vm_t vm, word_t unused) {
	vm_data_t value = _vm_pop(vm);
	vm_data_t d = _vm_pop(vm);
	event_t ev = (event_t) d->data;
	assert(d->type==DataObjUser&&ev->magic==EVENT_MAGIC);
	assert(value->type==DataFloat);
	ev->timestamp = value->data;
}


void _VM_CALL vm_op_evSetType(vm_t vm, word_t unused) {
	vm_data_t value = _vm_pop(vm);
	vm_data_t d = _vm_pop(vm);
	event_t ev = (event_t) d->data;
	assert(d->type==DataObjUser&&ev->magic==EVENT_MAGIC);
	assert(value->type==DataInt);
	ev->type = value->data;
}


void _VM_CALL vm_op_evSetCode(vm_t vm, word_t unused) {
	vm_data_t value = _vm_pop(vm);
	vm_data_t d = _vm_pop(vm);
	event_t ev = (event_t) d->data;
	assert(d->type==DataObjUser&&ev->magic==EVENT_MAGIC);
	assert(value->type==DataInt);
	ev->code = value->data;
}


void _VM_CALL vm_op_evSetValue(vm_t vm, word_t unused) {
	vm_data_t value = _vm_pop(vm);
	vm_data_t d = _vm_pop(vm);
	event_t ev = (event_t) d->data;
	assert(d->type==DataObjUser&&ev->magic==EVENT_MAGIC);
	if(ev->value.type&DataManagedObjectFlag) {
		vm_obj_deref_ptr(vm, (void*)ev->value.data);
	}
	ev->value.type=value->type;
	ev->value.data=value->data;
	if(ev->value.type&DataManagedObjectFlag) {
		vm_obj_ref_ptr(vm, (void*)ev->value.data);
	}
}


